﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml;
using System.IO;
using System.Windows.Forms; // for testing only

namespace GE_Database_Manager
{
    public class ADRE : Database
    {
        // paths to ADRE files
        private string condition;
        private string nameplate;
        private string kinematics;
        private string waveform;
        private string comments;
        private string configDB;
        private string key;
        private Dictionary<string, string> slotsChannels;
        private List<string> datFiles;
        private DateTime startDate;
        private DateTime endDate;
        private Standardized_Wrapper wrapper;
        private int progressStep;

        public ADRE(string condition, string nameplate, string kinematics, string waveform, string comments, string configDB)
        {
            // initialize paths to ADRE file
            this.condition = condition;
            this.nameplate = nameplate;
            this.kinematics = kinematics;
            this.waveform = waveform;
            this.comments = comments;
            this.configDB = configDB;
            this.key = (condition + nameplate + kinematics + waveform + comments + configDB);

            // dict to store ("SlotXChanX", "segment_id: point_name") and ("workorder", "someTextHere")
            slotsChannels = new Dictionary<string, string>();

            // list to store valid waveform files
            datFiles = new List<string>();
        }

        public string Condition
        {
            get { return condition; }
        }
        public override int ProgressStep
        {
            get { return progressStep; }
            set { progressStep = value; }
        }

        public string Nameplate
        {
            get { return nameplate; }
        }

        public string Kinematics
        {
            get { return kinematics; }
        }

        public string Waveform
        {
            get { return waveform; }
        }

        public override string Key
        {
            get { return key; }
            set { }
        }

        private bool ProcessXML()
        {
            try
            {
                // load the xml document
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.Load(configDB);

                // get workorder information
                XmlNode xmlNode = xmlDoc.DocumentElement.ChildNodes[0].ChildNodes[0].ChildNodes[1].ChildNodes[3].ChildNodes[0];
                if (xmlNode.HasChildNodes)
                {
                    if (string.IsNullOrEmpty(xmlNode.InnerText))
                        slotsChannels.Add("workorder", "Not on File");
                    else
                        slotsChannels.Add("workorder", xmlNode.InnerText);
                }

                // get engrUnit and point_name for each channel in each slot
                xmlNode = xmlDoc.DocumentElement.ChildNodes[0].ChildNodes[0].ChildNodes[3].ChildNodes[0].ChildNodes[3];
                if (xmlNode.HasChildNodes)
                {
                    int x = 0;
                    foreach (XmlNode slot in xmlNode.ChildNodes)
                    {
                        if (slot.ChildNodes[3].HasChildNodes)
                        {
                            int y = 0;
                            foreach (XmlNode channel in slot.ChildNodes[3].ChildNodes)
                            {
                                string engrUnit = null;
                                if (channel.ChildNodes[1].HasChildNodes)
                                {
                                    foreach (XmlNode property in channel.ChildNodes[1].ChildNodes)
                                    {
                                        if (property.Attributes["PropID"].Value == "14")
                                        {
                                            engrUnit = property.Attributes["UnitID"].Value;
                                        }
                                    }
                                }
                                slotsChannels.Add("Slot" + x + "Chan" + y, channel.ChildNodes[0].ChildNodes[0].InnerText + " : " + engrUnit);
                                y++;
                            }
                        }
                        x++;
                    }
                }
            }
            // return failure if any exceptions occured
            catch (XmlException) { return false; }

            // return success
            return true;
        }

        private bool ReconcileWaveformDataFiles()
        {
            try
            {
                // get .dat files from waveform directory
                string[] files = Directory.GetFiles(waveform, "*.dat");

                // keep only the files that have the correct format
                foreach (var slotChannel in slotsChannels)
                {
                    foreach (var file in files)
                    {
                        if (file.Contains("Box0" + slotChannel.Key + "Raw_")) 
                            datFiles.Add(file);
                    }
                }
            }

            // return failure if any exception occurs
            catch (DirectoryNotFoundException) { return false; }

            // return success
            return true;
        }

        private int ProcessStages(int machineID, string pointname, Dictionary<string, string> kinematicsXLS)
        {
            // define stage id variable
            int stageID = -1;

            // define stage via pointname and insert stage / get stage id
            if (pointname.Equals("Planetary"))
            {
                Fixed s1_fixed = (kinematicsXLS["s1 fixed"].ToLower() == "yes") ? Fixed.Ring : Fixed.Sun;
                stageID = this.wrapper.InsertStage(Designation.Planetary, machineID, s1_fixed, null, null,
                    Convert.ToInt32(kinematicsXLS["s1 sun_tooth_count"]), Convert.ToInt32(kinematicsXLS["s1 planet_count"]),
                    Convert.ToInt32(kinematicsXLS["s1 ring_tooth_count"]), Convert.ToInt32(kinematicsXLS["s1 planet_count"]));
            }
            else if (pointname.Equals("Intermediate"))
            {
                stageID = this.wrapper.InsertStage(Designation.Parallel, machineID, null, Convert.ToInt32(kinematicsXLS["s2 gear_tooth_count"]),
                    Convert.ToInt32(kinematicsXLS["s2 pinion_tooth_count"]), null, null, null, null);
            }
            else if (pointname.Equals("High Speed"))
            {
                stageID = this.wrapper.InsertStage(Designation.Parallel, machineID, null, Convert.ToInt32(kinematicsXLS["s3 gear_tooth_count"]),
                    Convert.ToInt32(kinematicsXLS["s3 pinion_tooth_count"]), null, null, null, null);
            }
            else
            {
                stageID = this.wrapper.InsertStage(Designation.Other, machineID, null, null, null, null, null, null, null);
            }
            
            // return stage id
            return stageID;
        }

        private string EngrUnitAbbreviation(string unitID)
        {
            if (unitID.Equals("29")) return @"in/s";
            else if (unitID.Equals("37")) return "Pa";
            else if (unitID.Equals("38")) return "psi";
            else if (unitID.Equals("40")) return "g";
            else if (unitID.Equals("62")) return "atm";
            else if (unitID.Equals("63")) return "bar";
            else if (unitID.Equals("64")) return "V";
            else if (unitID.Equals("67")) return "mil";
            else if (unitID.Equals("68")) return "um";
            else if (unitID.Equals("78")) return @"kg/cm^2";
            else return null; 
        }

        private bool ProcessWaveformData(int machineID, Dictionary<string, string> kinematicsXLS)
        {
            // set flag for first waveform and declare engrUnit
            bool firstWaveform = true;
            string engrUnitAbb = null;

            // loop through each slot
            int maxSlots = 3;
            for (int i = 0; i <= maxSlots; i++)
            {
                // loop through each channel
                int maxChans = 7;
                for (int j = 0; j <= maxChans; j++)
                {
                    // set sensorID flag
                    int stageID = -1;
                    int sensorID = -1;
                    bool nextSensor = true;

                    // get all waveforms
                    int maxWaveforms = 999;
                    for (int k = 0; k <= maxWaveforms; k++)
                    {
                        // build full file path
                        string directoryPath = Path.GetDirectoryName(datFiles[0]);
                        string fileName = @"\Box0Slot" + i + "Chan" + j + "Raw_" + k + ".dat";
                        string fullFilePath = directoryPath + fileName;

                        // check if the list of reconciled files contains the potential file
                        if (datFiles.Contains(fullFilePath))
                        {
                            // insert/get stage and sensorID once for eace new slot-chan and check
                            if (nextSensor)
                            {
                                string[] parts = slotsChannels["Slot" + i + "Chan" + j].Split(new string[] {" : "}, StringSplitOptions.RemoveEmptyEntries);
                                stageID = ProcessStages(machineID, parts[0].Trim(), kinematicsXLS);
                                if (stageID == -1) return false;
                                sensorID = this.wrapper.InsertSensor(parts[0].Trim(), stageID);
                                if (sensorID == -1) return false;
                                engrUnitAbb = EngrUnitAbbreviation(parts[1]);
                                if (engrUnitAbb.Equals(null)) return false;
                                nextSensor = false;
                            }

                            // bring file in as an array of bytes
                            FileInfo fi = new FileInfo(fullFilePath);
                            byte[] fiBytes = File.ReadAllBytes(fi.FullName);

                            // get start and stop bytes, to caculate waveform count
                            int start = BitConverter.ToInt32(fiBytes.Skip(36).Take(4).ToArray(), 0);
                            int stop = BitConverter.ToInt32(fiBytes.Skip(40).Take(4).ToArray(), 0);
                            int waveformCount = stop - start + 1;

                            // get EngScale
                            float engScale = ((float)BitConverter.ToInt32(fiBytes.Skip(16 + 140).Take(4).ToArray(), 0) / 65536);

                            // get VoltageFullScaleRange
                            float VoltageFullScaleRange = (float)BitConverter.ToInt32(fiBytes.Skip(20 + 140).Take(4).ToArray(), 0) / 65535;

                            // get VoltageRangeLower
                            float VoltageRangeLower = BitConverter.ToInt32(fiBytes.Skip(24 + 140).Take(4).ToArray(), 0) / 65536;

                            // get VoltageRangeUpper
                            float VoltageRangeUpper = BitConverter.ToInt32(fiBytes.Skip(28 + 140).Take(4).ToArray(), 0) / 65536;

                            // Get Coupled flag
                            int coupled = (int)fiBytes[33 + 140];

                            // get ADC Count
                            float ADC_count = BitConverter.ToUInt32(fiBytes.Skip(84 + 140).Take(4).ToArray(), 0);

                            // get RPM's
                            Single rpm = 1966080000 / (Single)BitConverter.ToUInt32(fiBytes.Skip(100 + 140).Take(4).ToArray(), 0);

                            // get Status
                            byte[] status = fiBytes.Skip(112 + 140).Take(4).ToArray();

                            // get datetime
                            DateTime gmt = new DateTime((Int64)BitConverter.ToUInt64(fiBytes.Skip(196 + 140).Take(8).ToArray(), 0)).AddYears(1600);

                            // get datetime from first and last waveforms
                            endDate = gmt;
                            if (firstWaveform)
                            {
                                firstWaveform = false;
                                startDate = gmt;
                            }

                            // caculate AC and DC VoltScale based on value of Coupled flag
                            float voltScale;
                            if (coupled % 2 == 0)
                                voltScale = (VoltageRangeUpper - VoltageRangeLower) / ADC_count;
                            else
                                voltScale = VoltageFullScaleRange / ADC_count;

                            // caculate dcVolt, this value is zero when AC
                            float dcVolt = (VoltageRangeUpper + VoltageRangeLower) / 2;

                            // bring the waveforms into standard engineering units
                            int offset = 140; // file header size
                            int sampleSize = (52 + 2048) * 4; // (header size + waveform sample size) * 4 bytes
                            List<float> calculatedWaveforms = new List<float>();
                            byte[] wave = fiBytes.Skip(offset).Take(sampleSize * waveformCount).ToArray(); // grab the entire waveform
                            
                            int byteCount = 0;
                            while (byteCount < (sampleSize * waveformCount))
                            {
                                if ((byteCount % sampleSize) == 0) byteCount += 208; // 208 = waveform header size 52 * 4
                                calculatedWaveforms.Add(((BitConverter.ToInt32(wave, byteCount) * voltScale) + dcVolt) / engScale);
                                byteCount += 4;
                            }

                            // convert list of caculated waveforms back to byte array
                            float[] floatArray_waveforms = calculatedWaveforms.ToArray();
                            byte[] byteArray_waveforms = new byte[floatArray_waveforms.Length * 4];
                            Buffer.BlockCopy(floatArray_waveforms, 0, byteArray_waveforms, 0, byteArray_waveforms.Length);

                            // get engrUnitID and check its good
                            int engrUnitID = this.wrapper.GetEngrUnitIdFromAbbreviation(engrUnitAbb);
                            if (engrUnitID == -1) return false;

                            // insert waveform and check its good
                            if (!this.wrapper.InsertWaveform(sensorID, gmt, rpm, 128000, 32, byteArray_waveforms, engrUnitID, status)) return false;

                            // ************************************************************************ //
                            // ************************ testing purposes only  ************************ //
                            // ************************************************************************ // 
                            // if (fileName == @"\Box0Slot0Chan2Raw_0.dat")
                            // {
                            //     using (StreamWriter file = new StreamWriter(@"\\psf\Home\Desktop\CS426\matlab-csharp\Project-GE-Error-Testing\csharp_test0.csv"))
                            //     {
                            //         foreach (float point in floatArray_waveforms)
                            //         {
                            //             file.Write(point.ToString("R") + Environment.NewLine);
                            //         }
                            //     }
                            // }
                            // ************************************************************************ //
                            // **************************** end of testing **************************** //
                            // ************************************************************************ // 
                        }
                        // no files are left to be processed
                        else
                        {
                            break; 
                        }
                    }
                }
            }

            // return success
            return true;
        }

        public override bool Process(Standardized_Wrapper wrapper)
        {
            // set the database name
            wrapper.CurrentDB = "ADRE";

            // instantiate this wrapper
            this.wrapper = wrapper;

            // get non unique ADRE data and update progress bar
            passProgress(this, new System.ComponentModel.ProgressChangedEventArgs(progressStep, "ADRE: Proccessing Nameplate Info.."));
            Tuple<string, string> nameplatePDF = ProcessPDF(nameplate);
            passProgress(this, new System.ComponentModel.ProgressChangedEventArgs(progressStep, "ADRE: Proccessing Malfunction Tag Info.."));
            String conditionTXT = ProcessMalfunctionTags(condition);
            passProgress(this, new System.ComponentModel.ProgressChangedEventArgs(progressStep, "ADRE: Proccessing Comments Info.."));
            String commentsTXT = ProcessComments(comments);
            passProgress(this, new System.ComponentModel.ProgressChangedEventArgs(progressStep, "ADRE: Proccessing Gearbox Kinematics Info.."));
            Dictionary<string, string> kinematicsXLS = ProcessExcel(kinematics);

            // get unique ADRE waveform data and check for success and update progress bar
            passProgress(this, new System.ComponentModel.ProgressChangedEventArgs(progressStep, "ADRE: Proccessing Database Configuration Info.."));
            if (!ProcessXML()) return false;

            // reconcile waveform data files against xml data and check for success
            if (!ReconcileWaveformDataFiles()) return false;

            // insert/get machineID and check its good
            int machineID = this.wrapper.InsertMachine(nameplatePDF.Item1, "gearbox",
                nameplatePDF.Item2, null, slotsChannels["workorder"], kinematicsXLS["gearbox_make"]);
            if (machineID == -1) return false;

            // insert/get stageID and check its good, for all three stages
            // if (!ProcessStages(machineID, kinematicsXLS)) return false;

            // insert/get sensorID and insert waveform data for each sensor, check its good and update progress bar
            passProgress(this, new System.ComponentModel.ProgressChangedEventArgs(progressStep, "ADRE: Proccessing Waveform Info.."));
            if (!ProcessWaveformData(machineID, kinematicsXLS)) return false;

            // insert machine condition information and check its good
            if (!wrapper.InsertMachineCondition(machineID, startDate, endDate, conditionTXT, commentsTXT, "ADRE")) return false;

            // return success
            return true;
        }
    }
}
